﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Reflection;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Unis.Framework.Core.Exceptions;
using Unis.Framework.Core.Log;
using Unis.Framework.Core.Utility;
using Unis.Framework.ORM.BaseDefiniens.Enums;
using Unis.Framework.ORM.Common;
using Unis.Framework.ORM.DbSql;
using Unis.Framework.ORM.Emit;
using Unis.Framework.ORM.Exceptions;
using Unis.Framework.ORM.Mapping;
using Unis.Framework.ORM.Xml;
using Unis.ORM.DbSql;
using Unis.ORM.Mapping;
using Unis.ORM.Persistence;
using Unis.ORM.Xml;

namespace Unis.Framework.ORM.Persistence
{
    public class PersistenceManagerHelper
    {
        // Fields
        private string[] _CommandTextFormatValues;
        private static readonly string SQL_PREFIX = SqlShareHelper.SQL_XML_CFG_PARAM_PREFIX;

        // Methods
        public PersistenceManagerHelper()
        {
        }

        public PersistenceManagerHelper(string[] commandTextFormatValues)
        {
            this._CommandTextFormatValues = commandTextFormatValues;
        }

        public void AddParamInfoToDbCommand(Database db, DbCommand dbCmd, SqlParamInfo parInfo)
        {
            this.AddParamInfoToDbCommand(db, dbCmd, parInfo, parInfo.Value);
        }

        public void AddParamInfoToDbCommand(Database db, DbCommand dbCmd, SqlParamInfo parInfo, object parValue)
        {
            if (parInfo.Overcast)
            {
                dbCmd.CommandText = this.ReaplaceSpecString(dbCmd.CommandText, parInfo.Name, parValue.ToString());
            }
            else if (parInfo.Direction == ParameterDirection.Output)
            {
                db.AddOutParameter(dbCmd, this.CreateParName(db, parInfo.Name), parInfo.DbType, parInfo.Length);
            }
            else if (parInfo.Name.ToUpper() == "@WHERE")
            {
                dbCmd.CommandText = this.ReaplaceSpecString(dbCmd.CommandText, parInfo.Name, parValue.ToString());
            }
            else
            {
                DbType dbType = this.ConvertToRealDbType(db, parInfo.DbType);
                db.AddInParameter(dbCmd, this.CreateParName(db, parInfo.Name), dbType, this.ConvertToRealDbValue(db, parValue, parInfo.DbType));
            }
        }

        public DbType ConvertToRealDbType(Database db, DbType dbType)
        {
            if (DatabaseConfigurationScope.GetDatabaseType(db) == DatabaseType.Oracle)
            {
                if (dbType == DbType.Boolean)
                {
                    return DbType.String;
                }
                if (dbType == DbType.String)
                {
                    return DbType.AnsiString;
                }
            }
            return dbType;
        }

        public object ConvertToRealDbValue(Database db, object val, DbType dbType)
        {
            if (DatabaseConfigurationScope.GetDatabaseType(db) != DatabaseType.Oracle)
            {
                return val;
            }
            if ((val == null) || (string.Compare(val.GetType().Name, "Boolean", true) != 0))
            {
                return val;
            }
            if (!((bool)val))
            {
                return "0";
            }
            return "1";
        }

        public DbCommand[] CreateDbCommandByDataRow(Database db, SqlString[] sqlStrs, DataRow drData)
        {
            List<DbCommand> list = new List<DbCommand>();
            foreach (SqlString str in sqlStrs)
            {
                IList<SqlParamInfo> paramFields = str.ParamFields;
                DbCommand sqlStringCommand = db.GetSqlStringCommand(str.SqlStr);
                foreach (SqlParamInfo info in paramFields)
                {
                    if ((info.Direction != ParameterDirection.Input) && (info.Direction != ParameterDirection.InputOutput))
                    {
                        throw new SqlArgumentXmlConfigException(str.SqlStr, info.MappingName);
                    }
                    if (!drData.Table.Columns.Contains(info.MappingName))
                    {
                        throw new SqlArgumentXmlConfigException(str.SqlStr, info.MappingName);
                    }
                    this.AddParamInfoToDbCommand(db, sqlStringCommand, info, drData[info.MappingName]);
                }
                sqlStringCommand.CommandText = this.ReplaceSqlParamsByDatabaseType(db, sqlStringCommand.CommandText);
                list.Add(sqlStringCommand);
            }
            return list.ToArray();
        }

        public DbCommand[] CreateDbCommandBySqlParams(Database db, string xmlFileName, string sqlName, List<SqlParamInfo> sqlParams)
        {
            DatabaseConfigurationScope.GetDatabaseType(db);
            SqlString[] sqlString = SqlConfigHelper.Instance.GetSqlString(xmlFileName, sqlName);
            if ((sqlString == null) || (sqlString.Length == 0))
            {
                throw new XmlSqlConfigNotExistsException(xmlFileName, sqlName);
            }
            List<DbCommand> list = new List<DbCommand>();
            Dictionary<string, SqlParamInfo> dictionary = new Dictionary<string, SqlParamInfo>();
            foreach (SqlParamInfo info in sqlParams)
            {
                if (!dictionary.ContainsKey(info.Name))
                {
                    dictionary.Add(info.Name, info);
                }
            }
            foreach (SqlString str in sqlString)
            {
                str.SqlStr = this.formatSqlString(str.SqlStr);
                IList<SqlParamInfo> paramFields = str.ParamFields;
                DbCommand sqlStringCommand = db.GetSqlStringCommand(str.SqlStr);
                if ((paramFields != null) && (paramFields.Count > 0))
                {
                    for (int i = 0; i < paramFields.Count; i++)
                    {
                        SqlParamInfo parInfo = paramFields[i];
                        if (!dictionary.ContainsKey(parInfo.Name))
                        {
                            throw new APPException(string.Format("调用XML文件:{0} 下的SQL {1} 配置的参数 {2} 在传入的参数中不存在！", xmlFileName, sqlName, parInfo.Name), APPMessageType.SysErrInfo);
                        }
                        SqlParamInfo info3 = dictionary[parInfo.Name];
                        this.AddParamInfoToDbCommand(db, sqlStringCommand, parInfo, info3.Value);
                    }
                }
                sqlStringCommand.CommandText = this.ReplaceSqlParamsByDatabaseType(db, sqlStringCommand.CommandText);
                list.Add(sqlStringCommand);
            }
            return list.ToArray();
        }

        public DbCommand[] CreateDbCommandByXml(Database db, string xmlFileName, string sqlName, params object[] parValues)
        {
            DatabaseConfigurationScope.GetDatabaseType(db);
            SqlString[] sqlString = SqlConfigHelper.Instance.GetSqlString(xmlFileName, sqlName);
            if ((sqlString == null) || (sqlString.Length == 0))
            {
                throw new XmlSqlConfigNotExistsException(xmlFileName, sqlName);
            }
            List<DbCommand> list = new List<DbCommand>();
            foreach (SqlString str in sqlString)
            {
                str.SqlStr = this.formatSqlString(str.SqlStr);
                IList<SqlParamInfo> paramFields = str.ParamFields;
                if (((paramFields != null) && (paramFields.Count > 0)) && ((parValues == null) || (parValues.Length != paramFields.Count)))
                {
                    throw new APPException("调用XML文件:" + xmlFileName + " 下的SQL" + sqlName + " 传入的参数和配置的参数不一致！");
                }
                DbCommand sqlStringCommand = db.GetSqlStringCommand(str.SqlStr);
                if ((paramFields != null) && (paramFields.Count > 0))
                {
                    for (int i = 0; i < paramFields.Count; i++)
                    {
                        SqlParamInfo parInfo = paramFields[i];
                        this.AddParamInfoToDbCommand(db, sqlStringCommand, parInfo, parValues[i]);
                    }
                }
                sqlStringCommand.CommandText = this.ReplaceSqlParamsByDatabaseType(db, sqlStringCommand.CommandText);
                list.Add(sqlStringCommand);
            }
            return list.ToArray();
        }

        public EntityDbCommandInfo[] CreateDeleteNotInDbCommand(Database db, SqlString[] sqlDeleteNotIn, DataRow[] drsData, string keyName, object foreingKeyValue)
        {
            List<EntityDbCommandInfo> list = new List<EntityDbCommandInfo>();
            foreach (SqlString str in sqlDeleteNotIn)
            {
                IList<SqlParamInfo> paramFields = str.ParamFields;
                DbCommand sqlStringCommand = db.GetSqlStringCommand(str.SqlStr);
                sqlStringCommand.CommandText = this.ReplaceSqlParamsByDatabaseType(db, sqlStringCommand.CommandText);
                TraceEx.Assert(paramFields.Count == 2, str.SqlStr + " SQL 语句配置需要2个参数,外键 和 主键的拼接字符串。");
                this.AddParamInfoToDbCommand(db, sqlStringCommand, paramFields[0], foreingKeyValue);
                string keyValuesToString = SqlShareHelper.Instance.GetKeyValuesToString(drsData, keyName);
                this.AddParamInfoToDbCommand(db, sqlStringCommand, paramFields[1], keyValuesToString);
                list.Add(new EntityDbCommandInfo(null, sqlStringCommand));
            }
            return list.ToArray();
        }

        public string CreateParName(Database db, string name)
        {
            return (string.Empty + ((name.IndexOf(SqlShareHelper.SQL_XML_CFG_PARAM_PREFIX) == 0) ? name.Substring(1, name.Length - 1) : name));
        }

        private string formatSqlString(string sqlString)
        {
            if (((sqlString.IndexOf("{0}") > 0) && (this._CommandTextFormatValues != null)) && (this._CommandTextFormatValues.Length > 0))
            {
                try
                {
                    return string.Format(sqlString, (object[])this._CommandTextFormatValues);
                }
                catch (Exception exception)
                {
                    throw new APPException(string.Format("格式化CommandText {0} 时出 错,{1}", sqlString, exception.Message), APPMessageType.SysErrInfo);
                }
            }
            return sqlString;
        }

        public DbCommand[] GetDbCommand(Database db, BaseModel entity, string sqlName)
        {
            ModelMappingInfo modelMappingInfo = AttMappingManager.Instance.GetModelMappingInfo(entity.GetType());
            SqlString[] sqlString = SqlConfigHelper.Instance.GetSqlString(modelMappingInfo.XmlConfigFileName, sqlName);
            List<DbCommand> list = new List<DbCommand>();
            foreach (SqlString str in sqlString)
            {
                DbCommand item = this.PrepareExecuteCommand(db, entity, str);
                list.Add(item);
            }
            return list.ToArray();
        }

        public DbCommand[] GetDbCommand(Database db, BaseModel entity, OperationType operationType, string[] propertys)
        {
            SqlString[] strArray = BaseSqlGenerator.GetSqlGeneratorObject(AttMappingManager.Instance.GetModelMappingInfo(entity.GetType()).ConfigOptions, propertys).GenerateSql(entity.GetType(), operationType, propertys);
            List<DbCommand> list = new List<DbCommand>();
            foreach (SqlString str in strArray)
            {
                DbCommand item = this.PrepareExecuteCommand(db, entity, str);
                list.Add(item);
            }
            return list.ToArray();
        }

        public DbCommand[] GetDbCommand(Database db, BaseModel entity, string xmlFileName, string sqlName)
        {
            SqlString[] sqlString = SqlConfigHelper.Instance.GetSqlString(xmlFileName, sqlName);
            List<DbCommand> list = new List<DbCommand>();
            foreach (SqlString str in sqlString)
            {
                DbCommand item = this.PrepareExecuteCommand(db, entity, str);
                list.Add(item);
            }
            return list.ToArray();
        }

        public DbCommand[] GetDbCommand<T>(Database db, IList<T> entitys, string xmlFileName, string sqlName)
        {
            SqlString[] sqlString = SqlConfigHelper.Instance.GetSqlString(xmlFileName, sqlName);
            List<DbCommand> list = new List<DbCommand>();
            foreach (SqlString str in sqlString)
            {
                Dictionary<string, DynamicPropertyAccessor> dycs = new Dictionary<string, DynamicPropertyAccessor>();
                foreach (SqlParamInfo info in str.ParamFields)
                {
                    DynamicPropertyAccessor accessor = new DynamicPropertyAccessor(typeof(T), info.MappingName);
                    dycs.Add(info.MappingName, accessor);
                }
                foreach (T local in entitys)
                {
                    DbCommand item = this.PrepareExecuteCommandByEmit(dycs, db, local, str);
                    list.Add(item);
                }
            }
            return list.ToArray();
        }

        public DbCommand PrepareExecuteCommand(Database db, BaseModel entity, SqlString sqlStr)
        {
            IList<SqlParamInfo> paramFields = sqlStr.ParamFields;
            DbCommand sqlStringCommand = db.GetSqlStringCommand(sqlStr.SqlStr);
            foreach (SqlParamInfo info in paramFields)
            {
                if ((info.Direction == ParameterDirection.Input) || (info.Direction == ParameterDirection.InputOutput))
                {
                    object parValue = Reflection.GetObjectProperty(entity, info.MappingName);
                    this.AddParamInfoToDbCommand(db, sqlStringCommand, info, parValue);
                    continue;
                }
                if (info.Direction == ParameterDirection.Output)
                {
                    this.AddParamInfoToDbCommand(db, sqlStringCommand, info);
                }
                else
                {
                    object obj3 = Reflection.GetObjectProperty(entity, info.MappingName);
                    this.AddParamInfoToDbCommand(db, sqlStringCommand, info, obj3);
                }
            }
            sqlStringCommand.CommandText = this.RemoveSqlStringLastSemicolon(db, sqlStringCommand.CommandText);
            sqlStringCommand.CommandText = this.ReplaceSqlParamsByDatabaseType(db, sqlStringCommand.CommandText);
            sqlStringCommand.CommandText = this.formatSqlString(sqlStringCommand.CommandText);
            return sqlStringCommand;
        }

        public DbCommand PrepareExecuteCommandByEmit(Dictionary<string, DynamicPropertyAccessor> dycs, Database db, object entity, SqlString sqlStr)
        {
            IList<SqlParamInfo> paramFields = sqlStr.ParamFields;
            DbCommand sqlStringCommand = db.GetSqlStringCommand(sqlStr.SqlStr);
            foreach (SqlParamInfo info in paramFields)
            {
                if ((info.Direction == ParameterDirection.Input) || (info.Direction == ParameterDirection.InputOutput))
                {
                    DynamicPropertyAccessor accessor = dycs[info.MappingName];
                    if (accessor == null)
                    {
                        throw new APPException(string.Format("参数MappingName :{0} 在类型{1}中找不到", info.MappingName, entity.GetType().FullName), APPMessageType.SysErrInfo);
                    }
                    object parValue = accessor.Get(entity);
                    this.AddParamInfoToDbCommand(db, sqlStringCommand, info, parValue);
                    continue;
                }
                if (info.Direction == ParameterDirection.Output)
                {
                    this.AddParamInfoToDbCommand(db, sqlStringCommand, info);
                }
                else
                {
                    DynamicPropertyAccessor accessor2 = dycs[info.MappingName];
                    if (accessor2 == null)
                    {
                        throw new APPException(string.Format("参数MappingName :{0} 在类型{1}中找不到", info.MappingName, entity.GetType().FullName), APPMessageType.SysErrInfo);
                    }
                    object obj3 = accessor2.Get(entity);
                    this.AddParamInfoToDbCommand(db, sqlStringCommand, info, obj3);
                }
            }
            sqlStringCommand.CommandText = this.RemoveSqlStringLastSemicolon(db, sqlStringCommand.CommandText);
            sqlStringCommand.CommandText = this.ReplaceSqlParamsByDatabaseType(db, sqlStringCommand.CommandText);
            return sqlStringCommand;
        }

        public string ReaplaceSpecString(string sqlStr, string oldStr, string newStr)
        {
            sqlStr = sqlStr.Replace(" " + oldStr + " ", " " + newStr + " ");
            sqlStr = sqlStr.Replace("(" + oldStr + ")", "(" + newStr + ")");
            sqlStr = sqlStr.Replace(" " + oldStr + ")", " " + newStr + ")");
            sqlStr = sqlStr.Replace(" " + oldStr + ",", " " + newStr + ",");
            sqlStr = sqlStr.Replace(" " + oldStr + ";", " " + newStr + ";");
            sqlStr = sqlStr.Replace(" " + oldStr + "(", " " + newStr + "(");
            return sqlStr;
        }

        public string RemoveSqlStringLastSemicolon(Database db, string sqlString)
        {
            if (DatabaseConfigurationScope.GetDatabaseType(db) == DatabaseType.Oracle)
            {
                int length = sqlString.Length;
                if (((sqlString.LastIndexOf(';') == (length - 1)) && (length > 4)) && (string.Compare(sqlString.Substring(length - 4, 4), "END;", true) != 0))
                {
                    sqlString = sqlString.Remove(length - 1, 1);
                }
            }
            return sqlString;
        }

        public string ReplaceSqlParamsByDatabaseType(Database db, string sqlString)
        {
            DatabaseType databaseType = DatabaseConfigurationScope.GetDatabaseType(db);
            switch (databaseType)
            {
                case DatabaseType.Oracle:
                    sqlString = sqlString.Replace(SqlShareHelper.SQL_XML_CFG_PARAM_PREFIX, SqlShareHelper.ORACLE_PARAM_PREFIX);
                    break;

                case DatabaseType.MSSQLServer:
                    sqlString = sqlString.Replace(SqlShareHelper.SQL_XML_CFG_PARAM_PREFIX, SqlShareHelper.SQL_SERVER_PARAM_PREFIX);
                    break;

                case DatabaseType.Sqlite:
                    sqlString = sqlString.Replace(SqlShareHelper.SQL_XML_CFG_PARAM_PREFIX, SqlShareHelper.ORACLE_PARAM_PREFIX);
                    break;

                case DatabaseType.MySql:
                    sqlString = sqlString.Replace(SqlShareHelper.SQL_XML_CFG_PARAM_PREFIX, SqlShareHelper.SQL_SERVER_PARAM_PREFIX);
                    break;

                default:
                    throw new APPException(string.Format("当前数据库类型不支持{0}", databaseType.ToString()), APPMessageType.SysDatabaseInfo);
            }
            return this.RemoveSqlStringLastSemicolon(db, sqlString);
        }

        public void SetDbCmdParamterBindByName(Database db, DbCommand dbCmd, bool byName)
        {
            if ((DatabaseConfigurationScope.GetDatabaseType(db) == DatabaseType.Oracle) && (dbCmd.GetType().FullName == "Oracle.DataAccess.Client.OracleCommand"))
            {
                PropertyInfo property = dbCmd.GetType().GetProperty("BindByName");
                if (property != null)
                {
                    property.SetValue(dbCmd, true, null);
                }
            }
        }

        // Properties
        public static PersistenceManagerHelper NewInstance
        {
            get
            {
                return new PersistenceManagerHelper(new string[0]);
            }
        }
    }
}
